package cas.ihep.spark.io;

import sun.misc.Cleaner;
import sun.nio.ch.DirectBuffer;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer;

@SuppressWarnings("WeakerAccess")
public class ByteBufferUtil {
    public static void releaseBuffer(ByteBuffer buf) {
        if (buf instanceof DirectBuffer) {
            Cleaner cleaner = ((DirectBuffer) buf).cleaner();
            if (cleaner != null) {
                cleaner.clean();
            }
        }
    }

    public static ByteBuffer sliceReadOnly(ByteBuffer buf){
        if(buf.isReadOnly()){
            return buf.slice();
        }
        try {
            int rem=buf.remaining();
            int pos=buf.position();
            int off=OFFSET.getInt(buf);
            int ConstructorOffset=1;
            Object initObject;
            if (buf.isDirect()) {
                ConstructorOffset|=2;
                initObject=buf;
            }else{
                initObject=buf.array();
            }
            return (ByteBuffer) Constructors[ConstructorOffset].newInstance(initObject,-1,0,rem,rem,pos+off);
        }catch (InvocationTargetException e){
            Throwable ie=e.getTargetException();
            if(ie instanceof RuntimeException){
                throw (RuntimeException)ie;
            }else if(ie instanceof Error){
                throw (Error)ie;
            }else{
                throw new RuntimeException(ie);
            }
        } catch (IllegalAccessException | InstantiationException e) {
            throw new RuntimeException(e);
        }
    }

    public static ByteBuffer sliceReadOnly(ByteBuffer buf,int offset,int length){
        int ConstructorOffset=1;
        try{
            int off=OFFSET.getInt(buf);
            int cap=buf.capacity();
            Object initObj;
            if(offset>=off && length>=0 && offset+length<=cap) {
                if (buf.isDirect()) {
                    ConstructorOffset |= 2;
                    initObj = buf;
                } else {
                    initObj = HB.get(buf);
                }
                return (ByteBuffer)Constructors[ConstructorOffset].newInstance(initObj,-1,0,length,length,off+offset);
            }
            throw new IllegalArgumentException();
        }catch (InvocationTargetException e){
            Throwable t=e.getTargetException();
            if(t instanceof RuntimeException){
                throw (RuntimeException)t;
            }else if(t instanceof Error){
                throw (Error)t;
            }else{
                throw new RuntimeException(t);
            }
        }catch (IllegalAccessException|InstantiationException e){
            throw new RuntimeException(e);
        }
    }

    public static ByteBuffer slice(ByteBuffer buf,int offset,int length){
        try {
            int off = OFFSET.getInt(buf);
            int cap=buf.capacity();
            if(offset>=off && length>=0 && offset+length<=cap){
                int ConstructorOffset=0;
                Object initObj;
                if(buf.isReadOnly()){
                    ConstructorOffset|=1;
                }
                if(buf.isDirect()){
                    ConstructorOffset|=2;
                    initObj=buf;
                }else{
                    initObj=HB.get(buf);
                }
                return (ByteBuffer)Constructors[ConstructorOffset].newInstance(initObj,-1,0,length,length,off+offset);
            }
            throw new IllegalArgumentException();
        } catch (IllegalAccessException |InstantiationException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            Throwable t=e.getTargetException();
            if(t instanceof RuntimeException){
                throw (RuntimeException)t;
            }else if(t instanceof Error){
                throw (Error)t;
            }else{
                throw new RuntimeException(t);
            }
        }
    }

    static final Constructor<?>[] Constructors = new Constructor[4];
    static final Field HB,OFFSET;
    static {
        try{
            Constructors[0]=Class.forName("java.nio.HeapByteBuffer").getDeclaredConstructor(byte[].class,int.class,int.class,int.class,int.class,int.class);
            Constructors[0].setAccessible(true);
            Constructors[1]=Class.forName("java.nio.HeapByteBufferR").getDeclaredConstructor(byte[].class,int.class,int.class,int.class,int.class,int.class);
            Constructors[1].setAccessible(true);
            Constructors[2]=Class.forName("java.nio.DirectByteBuffer").getDeclaredConstructor(DirectBuffer.class,int.class,int.class,int.class,int.class,int.class);
            Constructors[2].setAccessible(true);
            Constructors[3]=Class.forName("java.nio.DirectByteBufferR").getDeclaredConstructor(DirectBuffer.class,int.class,int.class,int.class,int.class,int.class);
            Constructors[3].setAccessible(true);
            HB=ByteBuffer.class.getDeclaredField("hb");
            HB.setAccessible(true);
            OFFSET=ByteBuffer.class.getDeclaredField("offset");
            OFFSET.setAccessible(true);
        }catch (ClassNotFoundException | NoSuchMethodException | NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }
}
